The three virtues of a programmer: Laziness, Impatience, and Hubris. – Larry Wall

Legacy:Reloading Weapons

From Unreal Wiki, The Unreal Engine Documentation Site
Jump to: navigation, search

This tutorial does NOT work in UT2004.

Attempted offline fix. No test, just using my memory... -Solid Snake

Reloading Weapons.[edit]

I've put quite a bit of effort in to making robust reloading code for the Weapons in the LawDogs mod. Since reloading is something that is potentially useful to everyone, I have made the code available here (minus all the stuff that doesn't relate to reloading).

It is complete with features allowing for sounds, animations and other effects, it works on-line and as far as I can tell it is virtually free of bugs. All the main code is contained in a weapon class. It is an abstract class, the actual weapons are subclasses of it.

class ReloadingWeapon extends Weapon
	abstract;
 
var() int ClipCount; //What's that you say? Clip is not a technically correct term? Do I care?
var() float ReloadRate; //Time it takes to insert one bullet.
var float ReloadTimer;
 
var() sound ReloadBeginSound, ReloadSound, ReloadEndSound; //Sounds played when start to reload, on insertion of each bullet, and when reloading has ended.
var() name ReloadAnim; //Animation to play when reloading is started.
var() float ReloadAnimRate;
 
var bool bIsReloading, bReloadEffectDone;
 
replication
{
	reliable if(Role == ROLE_Authority)
		ClipCount;
 
	//Functions called on the server from the client.
	reliable if(Role < ROLE_Authority)
		ReloadMeNow, FinishReloading;
 
	//Functions called on the client from the server.
	reliable if(Role == ROLE_Authority)
		ClientReload, ClientFinishReloading, ClientReloadEffects;
}
 
//So reloading can be bound to a key. Exec functions in weapons can only be called for the currently held weapon, which is perfect for this purpose.
exec function ReloadMeNow()
{
	if(!AllowReload())
		return;
 
	bIsReloading = true;
	ReloadTimer = Level.TimeSeconds;
	PlaySound(ReloadBeginSound, SLOT_Misc, TransientSoundVolume,,,, false);
	ClientReload();
}
 
//Called on the client when reloading starts.
simulated function ClientReload()
{
	bIsReloading = true;
	PlayAnim(ReloadAnim, ReloadAnimRate, 0.1);
}
 
//For effects during reloading, like smoke or shells ejected from the breech.
simulated function ClientReloadEffects(){}
 
//Reloading ends when the key is released.
exec function FinishReloading()
{
	if(!bIsReloading)
		return;
 
	PlaySound(ReloadEndSound, SLOT_Misc, TransientSoundVolume,,,, false);
	ClientFinishReloading();
	bIsReloading = false;
	bReloadEffectDone = false;
}
 
//Called on the client when reloading ends.
simulated function ClientFinishReloading()
{
	bIsReloading = false;
	PlayIdle();
 
	if(Instigator.PendingWeapon != None && Instigator.PendingWeapon != self)
		Instigator.Controller.ClientSwitchToBestWeapon();
}
 
//I compressed this to make it a little tider. I have no idea why I do this... is it harder to read or 
//do I just like smaller UC files...
function bool AllowReload()
{
	//Can't reload whilst firing, reloading, if the clip is full or if we don't have enough ammo.
	if((FireMode[0].IsFiring() || FireMode[1].IsFiring()) && 
           bIsReloading && ClipCount >= Default.ClipCount && 
           (Ammo[0] == None || Ammo[0].AmmoAmount <= ClipCount))
		return false;
	return true;
}
 
//Don't allow weapon switching whilst reloading.
simulated function bool PutDown()
{
	if(bIsReloading)
		return false;
 
	return Super.PutDown();
}
 
simulated function BringUp(optional Weapon PrevWeapon)
{
	Super.BringUp(PrevWeapon);
	bIsReloading = false;
}
 
//Reduce ClipCount every time a bullet is fired.
simulated function bool ConsumeAmmo(int Mode, float load, optional bool bAmountNeededIsMax)
{
	if(Ammo[Mode] != None)
	{
		if(Ammo[Mode].UseAmmo(int(load)) && load > 0)
			ClipCount--;
	}
}
 
simulated function bool HasAmmo()
{
	//Ignore FireMode[1] which doesn't use any ammo.
	return (Ammo[0] != None && FireMode[0] != None && Ammo[0].AmmoAmount >= FireMode[0].AmmoPerFire);
}
 
event WeaponTick(float dt)
{
	if(!bIsReloading)
	{
		//Stupid bots like to run around with an empty weapon, so force them to reload when appropriate.
		if(!Instigator.IsHumanControlled())
		{
			if(Level.TimeSeconds - Instigator.Controller.LastSeenTime > ClipCount)
				ReloadMeNow();
		}
	}
	else
	{
		//Add one bullet at a time as long as reloading key is held down.
		if(Level.TimeSeconds - ReloadTimer >= ReloadRate)
		{
			if(ClipCount >= Default.ClipCount) //Full.
			{
				ClipCount = Default.ClipCount;
				FinishReloading();
			}
			else if(Ammo[0].AmmoAmount <= ClipCount) //Out of ammo.
			{
				ClipCount = Ammo[0].AmmoAmount;
				FinishReloading();
			}
			else //Add another bullet.
			{
				PlaySound(ReloadSound, SLOT_Misc, TransientSoundVolume,,,, false);
				InsertBullet();
 
				if(ClipCount >= Default.ClipCount)
				{
					ReloadTimer = Level.TimeSeconds - (ReloadRate / 2);
				}
				else
					ReloadTimer = Level.TimeSeconds;
			}
		}
		else if(!bReloadEffectDone && Level.TimeSeconds - ReloadTimer >= ReloadRate / 2)
		{
			bReloadEffectDone = true;
			ClientReloadEffects();
		}
	}
}
 
//Can play animations for inserting individual bullets here, or other effects. 
//Server-side only, so should call a replicated function here and play animations in it, 
//rather than in this function itself.
function InsertBullet()
{
	ClipCount++;
}
 
//Show how many bullets left until reload.
simulated function float ChargeBar()
{
	local float CurrentClip, MaxClip;
 
	//Store the int values as floats to avoid rounding errors.
	CurrentClip = ClipCount;
	MaxClip = Default.ClipCount;
 
	return FMin(1, CurrentClip/MaxClip);
}
 
function byte BestMode()
{
	if(ClipCount > 0)
		return 0;
 
	return 1;
}
 
defaultproperties
{
	ClipCount=6
	ReloadRate=1.0
	ReloadAnim=Reload
	ReloadAnimRate=1.0
 
	bShowChargingBar=True
}

Bullets are inserted one at a time. If you wanted to completely fill the weapon in one go (as would be the case with most modern-style weapons) then change the bit in the InsertBullet function to ClipCount = Default.ClipCount

To have reloading triggered from a key press, two functions need to be bound to one key. For example, in User.ini:

E=ReloadMeNow | OnRelease FinishReloading

To make it easier to bind for end-users, you can make a custom GUIUserKeyBinding, which will make the bind show up in the controls menu. Here is an example:

class ReloadBinding extends GUIUserKeyBinding;
 
defaultproperties
{
	KeyData(0)=(KeyLabel="YourMod",bIsSection=True)
	KeyData(1)=(Alias="ReloadMeNow | OnRelease FinishReloading",KeyLabel="Reload")
}

As well as reloading from a key, it can also be done from alt-fire, like this:

class ReloadFire extends WeaponFire;
 
event ModeDoFire()
{
	ReloadingWeapon(Weapon).ReloadMeNow();
}
 
function StopFiring()
{
	ReloadingWeapon(Weapon).FinishReloading();
}
 
function bool IsFiring()
{
    return false;
}
 
defaultproperties
{
	bModeExclusive=true
	bWaitForRelease=true
	FireRate=0.2
	BotRefireRate=1.0
	AmmoClass=class'BallAmmo'
}

Last but not least, to ensure that the firemode(s) can only fire whilst there is ammo in the clip:

class ReloadFire extends ProjectileFire
	abstract;
 
var float LastClickTime;
 
var() Name EmptyAnim;
var() float EmptyAnimRate;
 
simulated function bool AllowFire()
{
	if(ReloadingWeapon(Weapon).bIsReloading)
		return false;
 
	if(ReloadingWeapon(Weapon).ClipCount < 1)
	{
		if(Level.TimeSeconds - LastClickTime > FireRate)
		{
			Weapon.PlayOwnedSound(NoAmmoSound, SLOT_Interact, TransientSoundVolume,,,, false);
			LastClickTime = Level.TimeSeconds;
 
			if(Weapon.HasAnim(EmptyAnim))
				weapon.PlayAnim(EmptyAnim, EmptyAnimRate, 0.0);
		}
 
		return false;
	}
 
	LastClickTime = Level.TimeSeconds;
 
	return Super.AllowFire();
 
}

In this case it is a ProjectileFire, but it applies exactly the same for InstantFire classes.

Category:Legacy Tutorial

Stagger: Does not seem to like UT2004 or vice versa. Does not compile as-is, spits out error over difference between consumeammo in reloadingweapon and weapon classes. Tried simply renaming consumeammo function, compiled successfully, but cannot select reloading weapon in game, gives the "(weapon name) is out of ammo" message, even if you enter allammo code.

Tarquin: Do you people not LOOK at what happens after you have posted? Please reformat your text, it is currently unreadable.

Tarquin: see http://forums.beyondunreal.com/showthread.php?t=141177 seems the code for this tute has a problem.

Solid Snake: I wonder if that works. I doubt it...

MythOpus: Stagger: It keeps saying the out of ammo message because you don't have a proper AmmoClass in the default properties of the weapon fire... If you subclass your weapons from the custom code above and add an ammo class it should work. Solid Snake: This code DOES work... just not too well :)

Solid Snake: Great, I just did some syntax fixes. When I whack in my own reloading code, I'll post it here as well. This one is a shotgun type reloading I think, a bullet at a time. I'll be doing both methods I think.

MythOpus: That explains a whole lot. I tried using this code for one of my mods awhile back... I used it for an assault rifle and it didn't work too well... I guess I know why now... Didn't think about it being for a shotgun :(. I cant wait for YOUR reloading code Though :D

Benoît Ce tut est vraiment sympa, ca fait un bout de temps que je cherche de la doc sur les replications. Merci. / This tut's really great. I've been looking for good tuts about replications for a while. Thanks.

Benoît : Are you sure about the declaration 'simulated function bool ConsumeAmmo(int Mode, float load, optional bool bAmountNeededIsMax)' (signature needed : function ConsumeAmmo(int Mode, float load)) ? I had a compilation error and had to modify a bit, but the rest works fine :)

Switch`: To make it work with UT2004's bNoAmmoInstances feature you may try following changes: (untested!)

  1. Replace all instances of Ammo[0].AmmoAmount with AmmoAmount(0)
  2. Replace all instances of Ammo[0]==None with AmmoAmount(0)==0
  3. Remove the overloaded HasAmmo() function from ReloadingWeapon source, in FireMode[1] set AmmoPerFire=0 in defaultproperties
  4. Use Super.ConsumeAmmo()
simulated function bool ConsumeAmmo( int Mode, float Load, optional bool bAmountNeededIsMax )
{
	if( Super.ConsumeAmmo(Mode, Load, bAmountNeededIsMax) )
	{
		if( Load > 0 )
			ClipCount--;
		return true;
	}
	return false;
}

Kohan: Bleh, it's not a magazine-based one. Can I warp some of your code to make it magazine-based? I'd put it in a separate tutorial, of course.

OlympusMons: Yeah clipcount is alittle misleading as it is actually the amount of ammo in the clip, a quick fix would be to do this so when you reload the complete ammo is added in one hit. This is untested but you could try...

function InsertBullet(){
    ClipCount=Default.ClipCount;
}

For a better version you could make clipcount the amount of clips by doing ammo amount x ammo per clip, which should give you the amount of clips. Then you can make a ammo in clip variable which would replace the old clipcount, Im kinda thinking out loud here so you'll have to work it out alittle more perhaps. Im pretty sure Im going in the right direction, there is replication to take into consideration as well I think.